Skip to content

Conversation

@chirino
Copy link
Contributor

@chirino chirino commented Jan 31, 2025

Fixes issue #1218

@chirino
Copy link
Contributor Author

chirino commented Jan 31, 2025

We should add some tests around the cache eviction edge cases:

  • Verify old sboms are dropped as the cache fills
  • Find out what happens when the cache is smaller a single request requires.

Comment on lines 44 to 66
pub(crate) fn set_approximate_memory_size(&self) -> PackageNode {
// Is there a better way to do this?
let size = size_of::<PackageNode>()
+ self.sbom_id.len()
+ self.node_id.len()
+ self.purl.iter().fold(0, |acc, purl|
// use the json string length as an approximation of the memory size
acc + serde_json::to_string(purl).unwrap_or_else(|_| "".to_string()).len())
+ self.cpe.iter().fold(0, |acc, cpe|
// use the json string length as an approximation of the memory size
acc + serde_json::to_string(cpe).unwrap_or_else(|_| "".to_string()).len())
+ self.name.len()
+ self.version.len()
+ self.published.len()
+ self.document_id.len()
+ self.product_name.len()
+ self.product_version.len();

PackageNode {
approximate_memory_size: size.try_into().unwrap_or(u32::MAX),
..self.clone()
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better way to estimate how much memory this thing uses up?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda assuming that saving the approximate_memory_size will give us savings later, it might not.

Copy link
Contributor Author

@chirino chirino Feb 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched to using the DeepSizeOf trait which has a derive macro and it seems to give much better results.

@chirino
Copy link
Contributor Author

chirino commented Jan 31, 2025

@JimFuller-RedHat I think I'm happy with this now. I'm going to keep it draft until #1217 goes in so you guys don't suffer the conflicts. But if you have idle time maybe worth a gander in case you think this went in the wrong direction.

@JimFuller-RedHat
Copy link
Contributor

@chirino this looks really great (you are amazing!) ... and yes its likely to change a little bit after our PR (will review in full once we get that one into main) ... one thought was do we need to expose an env var to control params for easy configuration ?

@chirino chirino marked this pull request as ready for review February 3, 2025 17:07
@chirino
Copy link
Contributor Author

chirino commented Feb 3, 2025

added cli option to allow for configuration, squashed and rebased off main.

#[arg(
id = "max-cache-size",
long,
env = "TRUSTD_MAX_CACHE_SIZE",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do

bytesize = "1.3"
chrono = { version = "0.4.35", default-features = false }
clap = "4"
moka = "0.12.10"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you consider also exposing moka expiration policies TTL and TTI as env vars ? I was hoping that we can control both time and mem dimensions ... Moka looks great!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah was considering it. But at least as this PR is, we should be able to avoid blowing up the memory usage which was the main goal of this PR.


self.graph.write().insert(distinct_sbom_id.to_string(), g);
let g = Arc::new(g);
self.graph_cache
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it occurred to me there might be times when we want to suspend caching (might be useful for debugging for example)... not sure if this is important or not to consider for right now

#[derive(Clone)]
pub struct AnalysisService {
graph: Arc<RwLock<GraphMap>>,
graph_cache: Arc<GraphMap>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would wait till @ctron reviews ... previously he reworked things in this area and it was a bit tricky.

@chirino chirino requested a review from ctron February 3, 2025 18:47
let all_graphs = service.load_all_graphs(&ctx.db).await?;
assert_eq!(all_graphs.len(), 2);

let big_sbom_size = service.cache_size_used() - small_sbom_size;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice !

Copy link
Contributor

@JimFuller-RedHat JimFuller-RedHat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apart from a few minor observations - looks great to me - as @ctron did the final fixes on the service invoke stuff good to get his eyeballs on it as well (as it was more then a little fiddly).

Copy link
Contributor

@ctron ctron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. A few things to iron out.

And I really would to have more nested use sections with every PR rather than less 😉

env = "TRUSTD_MAX_CACHE_SIZE",
default_value = "200 MB",
help = "Maximum size of the graph cache. You can use binary units like 'kb', 'mb' or 'gb'.",
value_parser = u64_from_size
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do

fmt,
ops::{Deref, DerefMut},
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please merge and nest this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. still wish tooling automated this.

&self,
query: impl Into<GraphQuery<'a>> + Debug,
distinct_sbom_ids: Vec<String>,
graphs: Vec<(String, Arc<PackageGraph>)>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it's not necessary to pass a Vec, so maybe use &[Arc<PackageGraph>] instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You right, will fix


impl AnalysisService {
pub fn render_dot(&self, sbom: &str) -> Option<String> {
pub fn render_dot(&self, graph: Arc<PackageGraph>) -> Option<String> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the Arc really required? Or is a &PackageGraph good enough?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ref is good enough.

};

Ok(InitData {
analysis: AnalysisService::new_sized(run.analysis.max_cache_size),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be great it one could construct an AnalysisService from an AnalysisConfig directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will change that around. And give AnalysisConfig a default.

/// of having its own cache. So creating a new instance should be a deliberate choice.
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self::new_sized(1024 * 1024 * 100)
Copy link
Contributor

@ctron ctron Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am worried this gets overlooked. It looks like we use this for tests only. One way could be to add #[cfg(test]. Or drop it an have a const default value for tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll just drop it and fix it in the tests.

{
let query = query.into();

// RwLock for reading hashmap<graph>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's not rwlock anymore, is there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will drop

Copy link
Contributor

@ctron ctron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd still prefer the use statements nested 😉

@chirino chirino added this pull request to the merge queue Feb 4, 2025
Merged via the queue into guacsec:main with commit 0b70b91 Feb 4, 2025
3 checks passed
@chirino chirino deleted the lru branch February 4, 2025 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants